#include "nntpwireclient.h"

#include <QDebug>
#include <QMutexLocker>
#include <QStringList>

NNTPWireClient::NNTPWireClient(QObject *parent) :
    QThread(parent)
{
    clientSocket = new QTcpSocket;

    connect(clientSocket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(clientSocketError(QAbstractSocket::SocketError)));
    connect(clientSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(clientSocketStateChanged(QAbstractSocket::SocketState)));

    clientState = ClientStateIdle;

    // Set the standard capability values
    clientCapabilities.version = 0;
    clientCapabilities.authinfo = false;
    clientCapabilities.hdr = false;
    clientCapabilities.ihave = false;
    clientCapabilities.implementation = QString("");
    clientCapabilities.list = false;
    clientCapabilities.modereader = false;
    clientCapabilities.newnews = false;
    clientCapabilities.over = false;
    clientCapabilities.post = false;
    clientCapabilities.reader = false;
    clientCapabilities.sasl = false;
    clientCapabilities.starttls = false;
    clientCapabilities.streaming = false;

    wokeUp = false;
    quit = false;
    ready = false;

    pipelineItems.clear();
}

NNTPWireClient::~NNTPWireClient()
{
    qDebug() << this << ": Delete NNTPWireClient";
    quit = true;
    cond.wakeOne();
    wait();

    delete clientSocket;
}

void NNTPWireClient::run()
{
    qDebug() << this << ": Starting NNTPWireClient thread";

    do {
        // Go to sleep if there's nothing to do.
        QMutexLocker locker(&mutex);
        if (!quit && clientState == ClientStateIdle) {
            qDebug() << this << ": Idle";

            ready = true;
            if (!pipelineItems.isEmpty()) {
                qDebug() << this << ": Calling nextPipelineItem from within run()";
                nextPipelineItem();
            } else {
                cond.wait(&mutex);
            }
        }

        /*
            5.1.  Initial Connection
            5.1.1.  Usage

                This command MUST NOT be pipelined.

                Responses [1]
                    200    Service available, posting allowed
                    201    Service available, posting prohibited
                    400    Service temporarily unavailable [2]
                    502    Service permanently unavailable [2]

                [1] These are the only valid response codes for the initial greeting;
                    the server MUST not return any other generic response code.

                [2] Following a 400 or 502 response, the server MUST immediately
                    close the connection.
       */
        if (clientState == ClientStateConnect && clientSocket->state() == QAbstractSocket::ConnectedState) {
            // Read all the data from the socket
            readFromSocket();

            // Set standard values
            postingAlowed = false;

            // If the incoming buffer is empty when the thread reaches to this point, a time out occured.
            // TODO: The buffer might not actually be empty but the time out could occure, this situation needs to be handled too.
            if (incomingBuffer.isEmpty()) {
                // Close the client socket
                clientSocket->close();
                // Clear all commands already in the pipeline
                pipelineItems.clear();
                // Emit a signal that an time out occured
                emit clientError(ClientErrorTimeOut);
            }

            // Check the response code
            int responseCode = incomingBuffer.left(3).toInt();
            qDebug() << this << ": incommingBuffer =" << incomingBuffer;
            qDebug() << this << ": Response code =" << responseCode;
            // If the service is not available
            if (responseCode == 400 || responseCode == 502) {
                // Close the client socket
                clientSocket->close();
                // Clear all commands already in the pipeline
                pipelineItems.clear();
                // Emit an error signal
                emit clientError(ClientErrorServerNotAvailable);
            } else if (responseCode == 200) {
                // Posting is allowed
                postingAlowed = true;
                // Emit a signal that the client has connected
                emit clientConnected();
            } else if (responseCode == 201) {
                // Posting is not allowed
                postingAlowed = false;
                // Emit a signal that the client has connected
                emit clientConnected();
            // If the response is not one of the four above
            } else {
                // Close the client socket
                clientSocket->close();
                // Clear all commands already in the pipeline
                pipelineItems.clear();
                // Emit a signal that an error occured
                emit clientError(ClientErrorWrongResponseCode);
            }

            qDebug() << this << ": Available bytes to read:" << clientSocket->bytesAvailable();
            // Set the state to idle
            clientState = ClientStateIdle;
        }

        if (clientState == ClientStateQuit && clientSocket->state() == QAbstractSocket::ConnectedState) {
            readFromSocket();

            if (clientSocket->bytesAvailable() <= 0) {
                qDebug() << this << ": incomingBuffer =" << incomingBuffer;
                clientState = ClientStateIdle;
            }
        }

        /*
            5.2.  CAPABILITIES
            5.2.1.  Usage

                This command is mandatory.

                Syntax
                    CAPABILITIES [keyword]

                Responses
                    101    Capability list follows (multi-line)

                Parameters
                    keyword    additional feature, see description
        */
        if (clientState == ClientStateCapabilities && clientSocket->state() == QAbstractSocket::ConnectedState) {
            // Read all the data from the socket
            readFromSocket();

            // Check if everything has been read. Because the response contains multiple lines, it will end with
            // a \r\n.\r\n sequence. As long as this sequence isn't found, the response is not complete.

            // The standard value is false
            bool gotEverything = false;
            // Set the sequence to test if the response is complete
            char endSequence[] = "\r\n.\r\n";
            // Check the end of the incoming buffer
            if (incomingBuffer.endsWith(endSequence)) {
                // Set to true is the response is complete
                gotEverything = true;
            }

            // When the response is complete, handle the response code and the capabilities
            if (gotEverything) {
                // Check the response code
                int responseCode = incomingBuffer.left(3).toInt();
                qDebug() << this << ": incommingBuffer =" << incomingBuffer;
                qDebug() << this << ": Response code =" << responseCode;
                // The response is correct and the list follows
                if (responseCode == 101) {
                    int lineNumber = 0;
                    QString capabilities = QString(incomingBuffer);
                    foreach (QString line, capabilities.split("\r\n")) {
                        if (lineNumber != 0) {
                            if (line.startsWith("VERSION")) {
                                clientCapabilities.version = line.split(" ").at(1).toInt();
                                qDebug() << this << ": I have version" << clientCapabilities.version;
                            }                            
                        }
                        ++lineNumber;
                    }

                // The response is unkown or not according to the specifications
                } else {
                    // Close the client socket
                    clientSocket->close();
                    // Clear all commands already in the pipeline
                    pipelineItems.clear();
                    // Emit a signal that an error occured
                    emit clientError(ClientErrorWrongResponseCode);
                }
                clientState = ClientStateIdle;
            }
        }

    } while (!quit);

    qDebug() << this << ": Exiting NNTPWireClient thread";
}

void NNTPWireClient::connectToServer(const QString& hostName, int port)
{
    qDebug() << this << ": connectToServer(" << hostName << ", " << port << ")";

    CommandConnect *commandConnect = new CommandConnect;
    commandConnect->clientState = ClientStateConnect;
    commandConnect->serverHostName = hostName;
    commandConnect->serverPort = port;

    pipelineItems << commandConnect;

    if (/*clientState == ClientStateIdle && isRunning()*/ready) {
        qDebug() << this << ": Calling nextPipelineItem from within connectToServer";
        nextPipelineItem();
    }
}

void NNTPWireClient::closeConnection()
{
    qDebug() << this << ": closeConnection()";

    CommandBase *commandQuit = new CommandBase;
    commandQuit->clientState = ClientStateQuit;

    pipelineItems << commandQuit;

    if (ready) {
        qDebug() << this << ": Calling nextPipelineItem from within closeConnection";
        nextPipelineItem();
    }
}

void NNTPWireClient::getCapabilities()
{
    qDebug() << this << ": getCapabilities()";

    CommandBase *commandCapabilities = new CommandBase;
    commandCapabilities->clientState = ClientStateCapabilities;

    pipelineItems << commandCapabilities;

    if (ready) {
        qDebug() << this << ": Calling nextPipelineItem from within getCapabilities";
        nextPipelineItem();
    }
}

void NNTPWireClient::nextPipelineItem()
{
    if (!pipelineItems.isEmpty()) {
        ready = false;
        incomingBuffer = QByteArray("");

        CommandBase *commandBase = pipelineItems.takeFirst();

        if (commandBase->clientState == ClientStateConnect) {
            CommandConnect *commandConnect = static_cast<CommandConnect *>(commandBase);
            clientSocket->connectToHost(commandConnect->serverHostName, commandConnect->serverPort);
            clientState = ClientStateConnect;
        }

        if (commandBase->clientState == ClientStateQuit) {
            QByteArray command = QByteArray("QUIT\r\n");
            clientSocket->write(command);
            clientState = ClientStateQuit;
        }

        if (commandBase->clientState == ClientStateCapabilities) {
            QByteArray command = QByteArray("CAPABILITIES\r\n");
            clientSocket->write(command);
            clientState = ClientStateCapabilities;
        }

        delete commandBase;

        qDebug() << this << ": Next pipeline item. State = " << clientState;

        if (!wokeUp) {
            wokeUp = true;
            QMetaObject::invokeMethod(this, "wakeUp", Qt::QueuedConnection);
        }
    }
}

void NNTPWireClient::wakeUp()
{
    QMutexLocker locker(&mutex);
    wokeUp = false;
    cond.wakeOne();
}

void NNTPWireClient::clientSocketError(QAbstractSocket::SocketError socketError)
{
    qDebug() << this << ": Socket error: " << socketError;
}

void NNTPWireClient::clientSocketStateChanged(QAbstractSocket::SocketState socketState)
{
    qDebug() << this << ": Socket state changed to: " << socketState;
}

void NNTPWireClient::readFromSocket()
{
    char buffer[10];
    qint64 totalRead = 0;
    //qint64 bytes = 5;
    qint64 bytesRead = 0;

    do {
        do {
            qDebug() << this << ": Reading...";
            bytesRead = clientSocket->read(buffer, sizeof(buffer));
            if (bytesRead <= 0)
                break;

            qDebug() << this << ": bytesRead = " << bytesRead;
//#if 0
            qint64 oldSize = incomingBuffer.size();
            incomingBuffer.resize(oldSize + bytesRead);
            memcpy(incomingBuffer.data() + oldSize, buffer, bytesRead);

            totalRead += bytesRead;
//#endif
        } while (/*totalRead < bytes*/ bytesRead > 0);
        if (bytesRead <= 0 && !incomingBuffer.isEmpty()) {
            qDebug() << this << ": Breaking the reading!";
            break;
        }
    } while (clientSocket->waitForReadyRead(30000));
}


















#if 0
NNTPWireClient::NNTPWireClient(QObject *parent) :
    QTcpSocket(parent)
{
    connect(this, SIGNAL(readyRead()), this, SLOT(clientReadyRead()));
    connect(this, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(clientError(QAbstractSocket::SocketError)));
    connect(this, SIGNAL(disconnected()), this, SLOT(clientDisconnected()));

    pstate = Idle;
}

void NNTPWireClient::clientReadyRead()
{
    qDebug() << this << ": clientReadyRead pstate =" << pstate;

    if (pstate == Idle)
        return;

    if (pstate == ConnectToHost) {
        int numRead = 0, numReadTotal = 0;
        char buffer[50];
        QString receivedData;

        forever {
            qDebug() << this << ": reading data...";
            numRead = read(buffer, 50);
            qDebug() << this << ": numRead =" << numRead;
            if (numRead > 0)
                receivedData = receivedData + QString::fromUtf8(buffer, numRead);
            qDebug() << this << ": receivedData =" << receivedData;

            numReadTotal += numRead;
            qDebug() << this << ": numReadTotal =" << numReadTotal;
            qDebug() << this << ": bytesAvailable =" << bytesAvailable();

            if (numRead == 0 && bytesAvailable() == 0) {
                qDebug() << this << ": [ConnectToHost] : received" << numReadTotal << "bytes: " << receivedData;
                emit connectedToNNTPServer();
                pstate = Idle;
                break;
            }
        }
        /*
        QString received = readAll();
        qDebug() << this << ": [ConnectToHost] :" << received;
        emit connectedToNNTPServer();
        pstate = Idle;
        */
    }

    if (pstate == GetCapabilities) {
        char buffer[10];
        qint64 totalRead = 0;
        qint64 bytes = 5;
        qint64 bytesRead = 0;
        do {
            bytesRead = read(buffer, sizeof(buffer));
            qDebug() << this << " : bytesRead =" << bytesRead;
            if (bytesRead <= 0)
                break;
            qint64 oldSize = incomingBuffer.size();
            incomingBuffer.resize(oldSize + bytesRead);
            memcpy(incomingBuffer.data() + oldSize, buffer, bytesRead);

            totalRead += bytesRead;
        } while (/*totalRead < bytes*/ bytesRead > 0);
        char e[] = "\r\n.\r\n";
        /*qDebug() << "---[Analyzing incomingBuffer]-----------------------------";
        qDebug() << "Total bytes read:" << totalRead;
        int c = 0;
        QString lineAscii;
        QString lineChar;
        for(int i = 0; i < totalRead; i++) {
            QString num = QString::number(int(incomingBuffer.at(i)));
            lineAscii = lineAscii + " " + num.rightJustified(3, '0');
            if ((int(incomingBuffer.at(i)) != 13) && (int(incomingBuffer.at(i)) != 10)) {
                QString ch = " " + QString(incomingBuffer.at(i)) + " ";
                lineChar = lineChar + " " + ch;
            } else {
                lineChar = lineChar + "    ";
            }
            c++;
            if (c > 10) {
                c = 0;
                qDebug() << lineAscii;
                qDebug() << lineChar;
                lineAscii = QString("");
                lineChar = QString("");
            }
        }
        qDebug() << lineAscii;
        qDebug() << lineChar;
        qDebug() << "----------------------------------------------------------";*/

        if (incomingBuffer.endsWith(e)) {
            qDebug() << this << ": [getCapabilities] :" << incomingBuffer;
        }
        /*
        int numRead = 0, numReadTotal = 0;
        char buffer[50];
        QString receivedData;

        forever {
            qDebug() << this << ": reading data...";
            numRead = read(buffer, 50);
            qDebug() << this << ": numRead =" << numRead;
            if (numRead > 0)
                receivedData = receivedData + QString::fromUtf8(buffer, numRead);
            qDebug() << this << ": receivedData =" << receivedData;

            numReadTotal += numRead;
            qDebug() << this << ": numReadTotal =" << numReadTotal;
            qDebug() << this << ": bytesAvailable =" << bytesAvailable();

            if (numRead == 0 && bytesAvailable() == 0) {
                qDebug() << this << ": [getCapabilities] : received" << numReadTotal << "bytes: " << receivedData;
                emit connectedToNNTPServer();
                pstate = Idle;
                break;
            }
        }
        */
        /*
        QString line;
        if (canReadLine()) {
            line = readLine();
        } else {
            return;
        }

        qDebug() << this << ": [GetCapabilities] :" << line;

        if (line == QString(".\r\n")) {
            // emit new capabilities
            qDebug() << this << ": [GetCapabilities] : At the end.";
            pstate = Idle;
        }
        */
    }

    if (pstate == Quit) {
        QString txt = readAll();
        qDebug() << this << ": [Quit] :" << txt;
        pstate = Idle;
    }
}

void NNTPWireClient::connectToNNTPServer()
{
    pstate = ConnectToHost;

    //ByteArray command = QByteArray("CAPABILITIES\r\n");
    //write(command);
}

void NNTPWireClient::getCapabilities()
{
    qDebug() << this << ": getCapabilities() pstate =" << pstate;
    /*
    if(!pstate == Idle) {
        qDebug() << "pstate is not idle when running getCapabilities";
        return;
    }*/

    pstate = GetCapabilities;
    qDebug() << this << ": getCapabilities() pstate =" << pstate;
    QByteArray command = QByteArray("CAPABILITIES\r\n");
    write(command);
}

void NNTPWireClient::authenticate()
{

}

void NNTPWireClient::quit()
{
    qDebug() << this << ": quit() pstate =" << pstate;
    qDebug() << this << " QUIT";
    pstate = Quit;

    qDebug() << this << ": quit() pstate =" << pstate;

    QByteArray command = QByteArray("QUIT\r\n");
    write(command);
}

void NNTPWireClient::clientError(QAbstractSocket::SocketError socketError)
{
    qDebug() << this << ": Error: " << socketError;
}

void NNTPWireClient::clientDisconnected()
{
    qDebug() << "Client disconnected";
}

bool NNTPWireClient::canTransferMore() const
{
    return bytesAvailable() > 0;
}

#if 0
NNTPWireClient::NNTPWireClient(QObject *parent) :
    QThread(parent)
{
    clientSocket = new QTcpSocket(this);
    //connect(clientSocket, SIGNAL(connected()), this, SLOT(clientConnected()));
    //connect(clientSocket, SIGNAL(readyRead()), this, SLOT(clientReadyRead()));

    pstate = Idle;
    quit = false;
    wokeUp = false;
    clientIsConnected = false;

    pipelineItems.clear();
}

void NNTPWireClient::run()
{
    qDebug() << this << ": Starting NNTPWireClient thread";
    do {
        // Go to sleep if there's nothing to do.
        QMutexLocker locker(&mutex);
        if (!quit && pstate == Idle) {
            qDebug() << this << ": Idle";
            //if (!pipelineItems.isEmpty()) {
            //    nextPipelineItem();
            //} else {
                cond.wait(&mutex);
            //}
        }

        if (pstate == ConnectToHost) {
            connect(clientSocket, SIGNAL(readyRead()), this, SLOT(clientConnected()));


            forever {
                QString received = clientSocket->readAll();
                qDebug() << clientSocket << ": [clientConnected] :" << received;
                emit connected();

                if(!clientSocket->waitForReadyRead(10000))
                    break;
            }

            pstate = Idle;
        }

        if (pstate == GetCapabilities) {
            //connect(clientSocket, SIGNAL(readyRead()), this, SLOT(clientCapabilities()));
            QByteArray command = QByteArray("CAPABILITIES\r\n");
            clientSocket->write(command);

            forever {
               QString txt = QString(clientSocket->readLine());
                if (txt == QString(".\r\n"))
                    break;
                qDebug() << this << txt;

                if(!clientSocket->waitForReadyRead(10000))
                    break;
            }

            pstate = Idle;
        }

        if (pstate == Authenticate) {
            connect(clientSocket, SIGNAL(readyRead()), this, SLOT(clientAuthenticate()));
            QByteArray command = QByteArray("");
            clientSocket->write(command);
            pstate = Busy;
        }

    } while (!quit);

    qDebug() << this << ": Exiting NNTPWireClient thread";
}

void NNTPWireClient::clientConnected()
{
    QString received = clientSocket->readAll();
    qDebug() << clientSocket << ": [clientConnected] :" << received;
    emit connected();
    pstate = Idle;

    clientIsConnected = true;

    // Disconnect client socket
    clientSocket->disconnect();

    //nextPipelineItem();
}

void NNTPWireClient::clientCapabilities()
{
    QString received = QString(clientSocket->readLine());
    qDebug() << this << ":" << received;

    while(clientSocket->waitForReadyRead(3000)) {
        QString txt = QString(clientSocket->readLine());
        if (txt == QString(".\r\n"))
            break;
        qDebug() << this << ": cap -> " << txt;
        qDebug() << this << "waiting...";
    }
    qDebug() << "Done";
    pstate = Idle;

    // Disconnect client socket
    clientSocket->disconnect();

    //nextPipelineItem();
}

void NNTPWireClient::clientAuthenticate()
{
    QString received = clientSocket->readAll();
    qDebug() << clientSocket << ": [clientAuthenticate] :" << received;
    //emit connected();
    pstate = Idle;

    // Disconnect client socket
    clientSocket->disconnect();

    nextPipelineItem();
}

void NNTPWireClient::connectToHost()
{
    qDebug() << this << ": Connecting to host";
    QMutexLocker locker(&mutex);

    pstate = ConnectToHost;

    if (!wokeUp) {
        wokeUp = true;
        QMetaObject::invokeMethod(this, "wakeUp", Qt::QueuedConnection);
    }
}

void NNTPWireClient::getCapabilities()
{
    qDebug() << this << ": Getting capabilities";
    QMutexLocker locker(&mutex);
    pstate = GetCapabilities;
    if (!wokeUp) {
        wokeUp = true;
        QMetaObject::invokeMethod(this, "wakeUp", Qt::QueuedConnection);
    }
    /*
    PipelineItem *pipelineItem = new PipelineItem;
    pipelineItem->pstate = GetCapabilities;
    pipelineItems << pipelineItem;

    if(pstate == Idle) {
        qDebug() << "pstate is idle, getting next pipeline element!";
        nextPipelineItem();
    }*/
}

void NNTPWireClient::authenticate()
{
    qDebug() << this << ": Authenticating";
    QMutexLocker locker(&mutex);

    PipelineItem *pipelineItem = new PipelineItem;
    pipelineItem->pstate = Authenticate;
    pipelineItems << pipelineItem;

    if(pstate == Idle)
        nextPipelineItem();
}

void NNTPWireClient::nextPipelineItem()
{
    if (!pipelineItems.isEmpty()) {
        pstate = pipelineItems.takeFirst()->pstate;

        if (!wokeUp) {
            wokeUp = true;
            QMetaObject::invokeMethod(this, "wakeUp", Qt::QueuedConnection);
        }
    }
}

void NNTPWireClient::wakeUp()
{
    QMutexLocker locker(&mutex);
    wokeUp = false;
    cond.wakeOne();
}

// Make a pipeline
// Hold a list with commands and run through them when the socket is available (after busy)
#endif
#endif
